// 模块范围
// const mongoose = require('mongoose')
// const extend = require('extend')
let counterSchema
let IdentityCounter

// 初始化插件，创建数据库中的计数器集合。
exports.initialize = function (mongoose, connection) {
  try {
    IdentityCounter = connection.model('IdentityCounter')
  } catch (ex) {
    if (ex.name === 'MissingSchemaError') {
      // 创建新的计数器模式。
      counterSchema = new mongoose.Schema({
        model: { type: String, required: true },
        field: { type: String, required: true },
        count: { type: Number, default: 0 },
      })

      // 使用“field”和“model”字段创建唯一索引。
      counterSchema.index({ field: 1, model: 1 }, { unique: true, required: true })

      // 使用新模式创建模型。
      IdentityCounter = connection.model('IdentityCounter', counterSchema)
    } else {
      throw ex
    }
  }
}

// 在自定义模式上调用插件时使用的函数。
exports.plugin = function (schema, options) {
  // 如果没有对 counterSchema 或 IdentityCounter 模型的引用，那么插件可能没有正确初始化，因此抛出错误。
  if (!counterSchema || !IdentityCounter) throw new Error('mongoose-auto-increment 尚未初始化')

  // 默认设置和插件作用域变量。
  let settings = {
    model: null, // 要为其配置插件的模型。
    field: '_id', // 插件应跟踪的字段。
    startAt: 0, // 计数应从此数字开始。
    incrementBy: 1, // 每次计数应增加的数量。
    unique: true, // 是否为该字段创建唯一索引
  }
  let fields = {} // 在 Mongoose 中添加属性的字段哈希。
  let ready = false // 如果计数器集合已更新且文档已准备好保存，则为真。

  switch (typeof options) {
    // 如果是字符串，用户选择仅传递模型名称。
    case 'string':
      settings.model = options
      break
    // 如果是对象，用户传递了一个选项哈希。
    case 'object':
      // extend(settings, options)
      Object.assign(settings, options)
      break
  }

  if (settings.model == null) throw new Error('必须设置模型')

  // 在模式中添加字段属性。
  fields[settings.field] = {
    type: Number,
    required: true,
  }
  if (settings.field !== '_id') fields[settings.field].unique = settings.unique
  schema.add(fields)

  // 查找此模型和相关字段的计数器。
  IdentityCounter.findOne({ model: settings.model, field: settings.field })
    .exec()
    .then((counter) => {
      if (!counter) {
        // 如果不存在计数器，则创建一个并保存它。
        counter = new IdentityCounter({
          model: settings.model,
          field: settings.field,
          count: settings.startAt - settings.incrementBy,
        })
        return counter.save()
      }
    })
    .then(() => {
      ready = true
    })
    .catch((err) => {
      throw err
    })

  // 声明一个函数以获取模型/模式的下一个计数器。
  const nextCount = function (callback) {
    IdentityCounter.findOne({ model: settings.model, field: settings.field })
      .exec()
      .then((counter) => {
        callback(null, counter === null ? settings.startAt : counter.count + settings.incrementBy)
      })
      .catch((err) => {
        callback(err)
      })
  }
  // 为方便起见，将 nextCount 添加为文档上的方法和模式上的静态方法。
  schema.method('nextCount', nextCount)
  schema.static('nextCount', nextCount)

  // 声明一个函数以将计数器重置为起始值 - 增量值。
  const resetCount = function (callback) {
    IdentityCounter.findOneAndUpdate(
      { model: settings.model, field: settings.field },
      { count: settings.startAt - settings.incrementBy },
      { new: true }
    )
      .exec()
      .then(() => {
        callback(null, settings.startAt)
      })
      .catch((err) => {
        callback(err)
      })
  }
  // 为方便起见，将 resetCount 添加为文档上的方法和模式上的静态方法。
  schema.method('resetCount', resetCount)
  schema.static('resetCount', resetCount)

  // 每次保存此模式中的文档时，运行此逻辑。
  schema.pre('save', function (next) {
    // 获取要保存的文档的引用。
    const doc = this

    // 仅在新文档时执行此操作
    if (doc.isNew) {
      // 声明自调用保存函数。
      ;(function save() {
        // 如果准备好了，运行增量逻辑。
        if (ready) {
          // 检查是否已经提供了一个数字，如果是，则更新计数器到该数字（如果大于当前计数）
          if (typeof doc[settings.field] === 'number') {
            IdentityCounter.findOneAndUpdate(
              { model: settings.model, field: settings.field, count: { $lt: doc[settings.field] } },
              { count: doc[settings.field] }
            )
              .exec()
              .then(() => {
                next()
              })
              .catch((err) => {
                next(err)
              })
          } else {
            // 查找此模型和字段的计数器集合条目并更新它。
            IdentityCounter.findOneAndUpdate(
              { model: settings.model, field: settings.field },
              { $inc: { count: settings.incrementBy } },
              { new: true }
            )
              .exec()
              .then((updatedIdentityCounter) => {
                // 如果没有错误，则继续并将文档的字段设置为当前计数。
                doc[settings.field] = updatedIdentityCounter.count
                next()
              })
              .catch((err) => {
                next(err)
              })
          }
        }
        // 如果尚未准备好，则设置一个 5 毫秒的计时器并尝试再次保存。它会一直这样做，直到计数器集合准备好为止。
        else {
          setTimeout(save, 5)
        }
      })()
    } else {
      next()
    }
  })
}
